#include "CyNetTCPClient.h"
#include "CyRoutine.h"
#include "resource.h"

#define TCP_NOFIFY 1048

CyNetTCPClient::CyNetTCPClient(HWND parentHWND)
{
	_closeHandler = NULL;
	_recvHandler = NULL;
	_sendHandler = NULL;
	_socket = INVALID_SOCKET;

	LPCTSTR ClsName = L"CyNetTCPClient";
	HINSTANCE hInstance = GetModuleHandle(NULL);
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= CyNetTCPClientProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= sizeof(LONG);
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= 0;
	wcex.lpszClassName	= ClsName;
	wcex.hIconSm		= 0;

	int err = RegisterClassEx(&wcex);

	_hWnd = CreateWindow(ClsName, 0, WS_OVERLAPPEDWINDOW,
		0, 0, 0, 0, NULL, NULL, hInstance, NULL);

	SetWindowLong(_hWnd, 0, (LONG) this);

	WSADATA wsadata;
    WSAStartup(0x0202, &wsadata);
}

CyNetTCPClient::~CyNetTCPClient(void)
{
	if (_closeHandler != NULL)
		delete _closeHandler;

	if (_recvHandler != NULL)
		delete _recvHandler;

	if (_sendHandler != NULL)
		delete _sendHandler;

	closesocket(_socket);

	SetWindowLong(_hWnd, 0, 0);

	DestroyWindow(_hWnd);
}

CyNetTCPClient& CyNetTCPClient::Connect(int PortNo, const char* IPAddress)
{
	SOCKADDR_IN target; //Socket address information

    target.sin_family = AF_INET; // address family Internet
    target.sin_port = htons (PortNo); //Port to connect on
    target.sin_addr.s_addr = inet_addr (IPAddress); //Target IP
	 
    _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //Create socket
    if (_socket == INVALID_SOCKET)
    {
		CY_THROW("Cannot create socket");
    }

    if (connect(_socket, (SOCKADDR *)&target, sizeof(target)) == SOCKET_ERROR)
    {
        CY_THROW("Cannot connect");
    }

	ResetRecvVariable();
	ResetSendVariable();

	WSAAsyncSelect(_socket, _hWnd, TCP_NOFIFY, FD_READ | FD_CLOSE | FD_WRITE);

	return *this;
}

CyNetTCPClient& CyNetTCPClient::Disconnect()
{
	closesocket(_socket);

	return *this;
}

CyNetTCPClient& CyNetTCPClient::Send(const CyNetObject& obj)
{
	if (!_sending)
	{
		_sendingSize = obj.GetStream(_sendBuffer, BUFFER_SIZE);
		_sending = true;
		NotifyWrite(0, 0, 0, 0);
	}

	return *this;
}

void CyNetTCPClient::ResetRecvVariable()
{
	_recieving = false;
	_recievingSize = 0;
	_recievedSize = 0;
}

void CyNetTCPClient::ResetSendVariable()
{
	_sending = false;
	_sendingSize = 0;
	_sentSize = 0;
}

void CyNetTCPClient::NotifyRead(HWND hwnd,
	UINT message, WPARAM wParam, LPARAM lParam)
{
	int _checksum;
	if (!_recieving) {
		// read the header
		_recievedSize += recv(_socket,
			_recvBuffer + _recievedSize, 12 - _recievedSize, 0);

		if (_recievedSize == 12) {	// read header complete
			_recieving = true;
			_recievingSize = *reinterpret_cast<int*>(_recvBuffer);
			_packetCode = *reinterpret_cast<int*>(_recvBuffer + 4);
			_checksum = *reinterpret_cast<int*>(_recvBuffer + 8);

			if ((_recievingSize ^ _packetCode) != _checksum) {
				CY_THROW("TCP object error");
			}
		}
	} else {
		// read the body
		_recievedSize += recv(_socket,
			_recvBuffer + _recievedSize, _recievingSize - _recievedSize, 0);

		if (_recievedSize == _recievingSize) {	// read body complete
			// Make packet
			CyNetObject * tcpObj = MakePacket(
				static_cast<CyNetObjectEnum>(_packetCode));
			tcpObj->FromStream(_recvBuffer);

			// Reset variable
			ResetRecvVariable();

			// Deliver packet to handler
			if (_recvHandler != NULL)
				_recvHandler->Do(hwnd, message, wParam, reinterpret_cast<LPARAM>(tcpObj));
		}
	}
}

void CyNetTCPClient::NotifyWrite(HWND hwnd,
	UINT message, WPARAM wParam, LPARAM lParam)
{
	if (_sending)
	{
		int err = send(_socket, _sendBuffer + _sentSize, _sendingSize - _sentSize, 0);

		if (err != -1)
		{
			_sentSize += err;
		}

		WCHAR buffer[100];
		swprintf_s(buffer, 100, L"%d\n", err);
		OutputDebugString(buffer);

		swprintf_s(buffer, 100, L"%d\n", WSAGetLastError());
		OutputDebugString(buffer);

		if (_sentSize == _sendingSize)
		{
			// reset status
			ResetSendVariable();

			// Notify
			if (_sendHandler != NULL)
				_sendHandler->Do(hwnd, message, wParam, lParam);
		}
	}
}

void CyNetTCPClient::NotifyClose(HWND hwnd,
	UINT message, WPARAM wParam, LPARAM lParam)
{
	if (_closeHandler != NULL)
	{
		_closeHandler->Do(hwnd, message, wParam, lParam);
	}
}

// Call back
LRESULT CALLBACK CyNetTCPClientProc(HWND hwnd,
	UINT message, WPARAM wParam, LPARAM lParam)
{
	CyNetTCPClient * _this = (CyNetTCPClient *) GetWindowLong(hwnd, 0);

	switch (message)
	{
	case TCP_NOFIFY:
		int err = WSAGETSELECTERROR(lParam);
		switch (WSAGETSELECTEVENT(lParam))
		{
		case FD_READ:
			_this->NotifyRead(hwnd, message, wParam, lParam);
			break;

		case FD_WRITE:
			_this->NotifyWrite(hwnd, message, wParam, lParam);
			break;

		case FD_CLOSE:
			_this->NotifyClose(hwnd, message, wParam, lParam);
			break;
		}
		return 0;
	}

	return DefWindowProc (hwnd, message, wParam, lParam) ;
}

// Packet Factory
CyNetObject * CyNetTCPClient::MakePacket(CyNetObjectEnum type)
{
	switch (type)
	{
	case CyNetObjectEnum::TEXT:
		return new CyNetText();

	case CyNetObjectEnum::DUMMY:
		return new CyNetDummy();

	case CyNetObjectEnum::ACCELEROMETER:
		return new CyNetAccelerometer();
	
	default:
		CY_THROW("Object type not found");
	}

	return 0;
}